/*
 * -- GoogleRateClient.java --
 * 
 * Copyright 2011, Kyan He <kyan.ql.he@gmail.com>
 * 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package im.kyan.android.googlefight;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.net.URLEncoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;

import android.util.Log;

public class GoogleRateClient implements IRateClient {
    
    private static final String TAG = "GoogleRateClient";
    
    private static final String HOST = "http://www.google.com/";
    
    private static final String REGEX = "<div id=resultStats>About ([0-9,]+) results<nobr>  \\(([0-9.]+) seconds\\)";
    
    private HttpClient mHttpClient;
    
    private CookieStore mCookieStore;
    
    private HttpContext mHttpContext;
    
    /**
     * Indicates whether we are in NCR context, synchronized
     */
    private boolean mIsNcrContext;
    
    public GoogleRateClient() {
        new NcrConnectThread().start();
    }
    
    /**
     * timer used for fail-try/heart-keep
     */
    private class Timer {
        
        // 10 minutes
        static final int RECONNECT_TIME = 10 * 60 * 1000;
        
        // fail-try timeout
        int timeout = 200;
        int lastTimeout = 0;
        // timer run times since start
        int start = 0;
        
        boolean isReset;
        
        // reset timer
        public void reset() {
            timeout = 200;
            start++;
            isReset = true;
        }

        // calculate next timer time
        public void start() {
            int tempTimeout = timeout;
            
            // fabinaqq queue
            timeout += lastTimeout;
            lastTimeout = tempTimeout;
            
            start++;
            isReset = false;
        }
        
        // get the current timer time according to timer status(isReset)
        public int getTime() {
            if (isReset) {
                return RECONNECT_TIME;
            } else {
                return timeout;
            }
        }
        
        // get start times of timer
        public int getCount() {
            return start;
        }
    }
    
    /**
     * Thread to keep connection with server
     */
    private class NcrConnectThread extends Thread {
        
        public NcrConnectThread() {
            super("ncr_connect_thread");
        }
        
        @Override
        public void run() {
            
            // mutex with getRate()
            synchronized (GoogleRateClient.this) {
                
                // Initialize member here to improve performance
                mHttpClient = new DefaultHttpClient();
                mCookieStore = new BasicCookieStore();
                mHttpContext = new BasicHttpContext();
                
                // set socket wait and syn timeout
                HttpParams params = mHttpClient.getParams();
                HttpConnectionParams.setTcpNoDelay(params, true);
                HttpConnectionParams.setSoTimeout(params, 15000);
                HttpConnectionParams.setConnectionTimeout(params, 15000);
                
                // avoid Circular redirect
                HttpClientParams.setRedirecting(params, false);
                
                Timer timer = new Timer();
                
                while (true) {
                    
                    // keep cookie persistent
                    mHttpContext.setAttribute(ClientContext.COOKIE_STORE,
                            mCookieStore);
                    
                    /* no country redirect */
                    HttpGet httpget = new HttpGet(HOST + "ncr");
                    HttpResponse response = null;
                    try {
                        response = mHttpClient.execute(httpget, mHttpContext);
                    } catch (Exception e) {
                        Log.d(TAG, "request NCR : " + e);
                        e.printStackTrace();
                    }
                    
                    if (response == null) {
                        
                        timer.start();
                        mIsNcrContext = false;
                        Log.d(TAG, "NCR failed");
                    } else {
                        
                        try {
                            response.getEntity().consumeContent();
                        } catch (IOException e) {
                        }
                        
                        // reconnect to server every 5min
                        timer.reset();
                        mIsNcrContext = true;
                        Log.d(TAG, "NCR ready");
                    }
                    
                    // wait for next loop
                    try {
                        final int timeout = timer.getTime();
                        
                        Log.d(TAG, "scheduel #" + timer.getCount()
                                + " re-try after " + timeout + "ms");
                        GoogleRateClient.this.wait(timeout);
                    } catch (InterruptedException e) {
                    }
                }
            }
        }
    }
    
    /**
     * @see IRateClient#getRate(String)
     */
    synchronized public long getRate(String key) throws SocketTimeoutException {
        
        // cannot get available rate
        if (!mIsNcrContext) {
            return -1;
        }
        
        // encode URL to avoid HTTP related exception
        String url = HOST + "search?q=" + URLEncoder.encode(key);
        HttpGet httpget = new HttpGet(url);
        HttpResponse response;
        
        Log.d(TAG, "request " + url);
        try {
            response = mHttpClient.execute(httpget, mHttpContext);
        } catch (Exception e) {
            Log.d(TAG, "execute request: " + e);
            throw new SocketTimeoutException();
        }
        if (200 != response.getStatusLine().getStatusCode()) {
            return -1;
        }
        
        HttpEntity entity = response.getEntity();
        Log.d(TAG, "Content-length: " + entity.getContentLength());
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        
        // read HTML to string
        int bytesread = 0;
        if (entity != null) {
            try {
                InputStream instream = entity.getContent();
                byte[] buf = new byte[4096];
                int sz;
                
                while ((sz = instream.read(buf)) != -1) {
                    bytesread += sz;
                    os.write(buf, 0, sz);
                }
            } catch (IOException e) {
                Log.d(TAG, "read stream: " + e);
                throw new SocketTimeoutException();
            }
        }
        String str = new String(os.toByteArray());
        Log.d(TAG, str.length() + " / " + bytesread);
        
        // match regex
        Matcher matcher = Pattern.compile(new String(REGEX.getBytes()))
                .matcher(str);
        if (matcher.find()) {
            Log.d(TAG, matcher.group());
            return Long.parseLong(matcher.group(1).replaceAll(",", ""));
        } else {
            return 0;
        }
    }
}